﻿<html>
<head>
    <title>Chirpr</title>
	<link rel="stylesheet" type="text/css" href="css/default.css" />
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/util.js"></script>
	<script type="text/javascript" src="js/chirpr.js"></script>
	<script type="text/javascript">//<![CDATA[
	/***************************************************************************
	 * Main View                                  http://www.codeplex.com/chirpr
	 *                                          Microsoft Public License (Ms-PL)
	 * Authors
	 *  - Charles Schmidt   2007-08  http://www.pickelhau.be
	 *  - Michael Flanakin  2008-09  http://www.michaelflanakin.com
	 *
	 * page
	 *  - alerts   : special notification handling
	 *  - messages : gets latest status updates from Twitter
	 *  - other    : init(), load(), fire(), resize()
	 **************************************************************************/
	var page =
	{
		//***| properties |*****************************************************
		
		post        : null,
		container   : null,
		alert       : null,
		content     : null,
		twitterLogo : null,
		scroll      : null,
		updateIcon  : null,
		logger      : null,
		

		/***********************************************************************
		 * page.init : initialization logic
		 **********************************************************************/
		init: function()
		{
			// log
			chirpr.logInit("main", "page.init", gadget.logging.entryPoint);
			
			// load controls
			page.post          = $('#post')[0];
			page.refresh       = $('#refresh')[0];
			page.container     = $('#container')[0];
			page.alert         = $('#alert')[0];
			page.alert.message = $('#alertmsg')[0];
			page.content       = $('#content')[0];
			page.twitterLogo   = $('#twitterLogo')[0];
			page.scroll        = $('#scroll')[0];
			page.updateIcon    = $('#updateIcon')[0];
			page.logger        = $('#debug:log')[0];
			
			// validate that all controls have loaded successfully
			if (page.post==null || page.container==null || page.alert==null || page.alert.message==null || page.content==null || page.scroll==null || page.updateIcon==null)
			{
				// TODO: force collapse to checkpoint
				chirpr.logInit("main", "page.init", gadget.logging.exitPoint, false, "one or more page controls were not found");
				return;
			}

			// check for update
			chirpr.logInit("main", "page.init", "kick off update check");
			page.checkForUpdate();

			// init alert
			page.alert.hide = page.alerts.hide;  // shortcut for simplicity and to lessen confusion (hopefully)
			page.alert.show = page.alerts.show;  // shortcut for simplicity and to lessen confusion (hopefully)
			page.alert.show(page.alerts.init);

			// set default size
			page.resize(chirpr.displaySize);
			if (gadget.host.browser) { page.container.style.border = "solid 1px #fff"; }
			
			// attach event handlers
			if (gadget.host.browser)
			{
				$("#debug:settings").style.display = "block";
				gadget.logging.registerListener(page.log);
			}
			else
			{
				System.Gadget.onSettingsClosed  = function(event)
				{
					if (event == null || event.closeAction == event.Action.cancel) { return; }
					page.messages.updateDate = null;
					chirpr.twitter.lastMessage = 0;
					page.alerts.show(page.alerts.settingsSaved);
				};
				System.Gadget.visibilityChanged = function() { if (gadget.host.sidebar && System.Gadget.visible) { page.load(); } };
			}
			
			// save message update to document so it can be accessed by flyout
			document.updateMessages = page.messages.update;
			
			// load page
			chirpr.logInit("main", "page.init", "calling page.load()");
			page.load();
			chirpr.logInit("main", "page.init", gadget.logging.exitPoint);
		},


		/***********************************************************************
		 * page.load : loads gadget
		 **********************************************************************/
		load: function()
		{
			// if there are no credentials, wait and try again
			if (!chirpr.hasCredentials() && !gadget.host.offline) { setTimeout("page.load();", 300); return; }
			chirpr.logInit("main", "page.load", gadget.logging.entryPoint, true, "getting messages for first time...");
			page.messages.update(true);
			chirpr.logInit("main", "page.load", gadget.logging.exitPoint);
		},


		/***********************************************************************
		 * page.checkForUpdate : checks for the latest gadget update
		 **********************************************************************/
		checkForUpdate: function()
		{
			if (chirpr.isUpdateAvailable()) { page.updateIcon.style.display = "inline"; return; }
			setTimeout("page.checkForUpdate();", chirpr.updateCheckInterval); 
		},

		
		/***********************************************************************
		 * page.fire : triggers other event from shortcut
		 **********************************************************************/
		fire: function(id)
		{
			var $a = $('#'+id);
			if ($a.length > 0) { $a[0].click(); window.focus(); }
		},

		
		/***********************************************************************
		 * page.resize : resizes the gadget
		 **********************************************************************/
		resize: function(height)
		{
			// save height
			gadget.settings.set(chirpr.properties.displaySize, height);
			
			// resize
			document.body.style.height  = height;
			page.container.style.height = height-25;
			page.twitterLogo.style.top  = height-20;
			page.scroll.style.top       = height-44;
		},
		

		/***********************************************************************
		 * page.log : logs a message to the listener
		 **********************************************************************/
		log: function(msg)
		{
			page.logger.innerText = msg + "\n" + page.logger.innerText;
		},
		
		
		/***********************************************************************
		 * page.alerts
		 *  - hide : stops showing the current alert
		 *  - show : shows an alert
		 **********************************************************************/
		alerts:
		{
			id            : 0,
			init          : 0,
			connecting    : 1,
			settingsSaved : 2,
			cannotConnect : 3,
			
			
			//***| shows the specified alert |**********************************
			
			show: function(type)
			{
				// init vars
				var messages = new Array(); // NOTE: max length is ~55 chars for each message -- always test in smallest view to ensure correct display
				messages[0]  = "Hi, my name is Chirpie. Welcome to Chirpr.";
				
				// if type is a string/object, need to use clean value
				type = (typeof(type)=="number" ? type : (typeof(type)=="string" ? type.replace("\"","'") : "["+typeof(type)+"]"));
				
				// add custom messages
				switch (type)
				{
					case page.alerts.init:
						if (chirpr.hasCredentials() || gadget.host.offline)
						{
							messages[0] = "<b>*yawn*</b> Hold on, I'm still waking up... <b>*stretch*</b>";
							messages[1] = "I'll ask about your friends shortly...";
							messages[2] = "Thanks for using Chirpr!";
						}
						else
						{
							messages[1] = "Before we start, I'll need to know a little about you.";
							messages[2] = "Click the wrench <img src='img/gadgetsettings_small.png' alt='wrench (aka gadget settings) icon' style='vertical-align:middle;'/><br/>and we can get started.";
							messages[3] = "After that, we'll see what your friends are up to.";
						}
						break;
					
					case page.alerts.connecting:
						messages[0] = "Hold tight, I'm asking about your friends, now...";
						break;
					
					case page.alerts.settingsSaved:
						messages[0] = "Got it! Thanks for keeping me up-to-date.";
						break;
						
					case page.alerts.cannotConnect:
						messages[0] = "Uh oh! Mr Twitter isn't responding.";
						messages[1] = "Did you accidentally delete the web!?";
						messages[2] = "I'll let you know when it's back up.";
						break;
						
					default:
						messages = page.alerts.getErrorMessages(type);
						break;
				}
				
				// show message
				page.alert.style.display = "block";
				page.alert.messages = messages;
				page.alerts.nextMessage(++page.alerts.id);
			},
			
			
			//***| hides the alert |********************************************
			
			hide: function()
			{
				page.alert.style.display = "none";
			},
			
			
			//***| converts an error code to a friendly message |***************
			
			getErrorMessages: function(code)
			{
				var messages = new Array();
				messages[0] = "What'd you do!? I don't know what happened.";
				messages[1] = "Mr Twitter gave me a note with #"+code+" on it. Maybe it's a lottery number.";
				var meaning = "an unknown";
				switch (code)
				{
					case 304  : meaning="a 'not modified'";        messages[0]="Nothing to see here. There aren't any new updates.";       messages[1]="No, seriously, I got nothing else for ya.";break;
					case 400  : meaning="a 'bad request'";         messages[0]="Wait. What? Mr Twitter doesn't understand me.";            messages[1]="You may have exceeded the rate limit (100/hr).";break;
					case 401  : meaning="a 'not authorized'";      messages[0]="Have you met Mr Twitter? He doesn't remember.";            messages[1]="Let's make sure I have your name right...";break;
					case 403  : meaning="a 'forbidden'";           messages[0]="Mr Twitter won't let you go in there. Sorry.";             messages[1]="Maybe if you ask nicer next time...";break;
					case 404  : meaning="a 'not found'";           messages[0]="I swear, if my head wasn't screwed on...";                 messages[1]="I can't seem to find what I was looking for.";break;
					case 500  : meaning="an 'internal server'";    messages[0]="<b>*bang, crash, boom*</b> We might want to come back.";   messages[1]="It looks like Mr Twitter has some issues to work out.";break;
					case 502  : meaning="a 'bad gateway'";         messages[0]="Mr Twitter's place is closed for renovations.";            messages[1]="Maybe we'll get that pool we've been wanting!";break;
					case 503  : meaning="a 'service unavailable'"; messages[0]="<b>[Now serving #32]</b> Look at that line! We got #468."; messages[1]="This may take a while. Let's come back later.";break;
					case -1   : meaning="'no credentials'";        messages[0]="Who is this, again? I'm sorry, I can be forgetful.";       messages[1]="Can you remind me what your Twitter username is?";break;
					case -999 :                                    messages[0]="Uhh... What happened? That's not good :-|";                messages[1]="<a href='http://www.codeplex.com/chirpr/Thread/List.aspx'>Tell the Chirpr team</a> about this, if it continues."; break;
					default:
						if (typeof(code)=="number")      { messages[1] = "I received error #"+code; }
						else if (typeof(code)=="string" && code.charAt(0) == "[" && code.charAt(code.length-1) == "]") { messages[1]="I can't handle this error (type "+code+"). Sorry :-("; }
						else if (typeof(code)=="string") { messages[1] = "Error: "+(code.length<=40 ? code : code.substring(0, 40)+"..."); }
						else { messages[1]="I don't know this error/code (not string/number). Sorry :-("; }
						break;
				}
				messages[2] = "In other words, I got "+meaning+" error.";
				return messages;
			},
			
			
			//***| shows the next message of the alert |************************
			
			nextMessage: function(id)
			{
				// if alert is now hidden, stop toggling messages
				if (page.alert.style.display == "none") { return; }
				
				// if existing alert id is not set or is newer, stop toggling messages; otherwise, update alert id
				if (id < page.alerts.id) { return; }
				
				// if there are no messages for the alert, hide the alert
				if (typeof(page.alert.messages) == "undefined" || page.alert.messages == null || page.alert.messages.length == 0) { page.alerts.hide(); }
				
				// if message index is not set or invalid, restart at 0
				if (typeof(page.alert.index) != "number" || page.alert.index < 0 || page.alert.index >= page.alert.messages.length) { page.alert.index = 0; }
				
				// set id
				page.alert.message.innerHTML = page.alert.messages[page.alert.index++];
				setTimeout("page.alerts.nextMessage("+id+");", 5000);
			}
		},


		/***********************************************************************
		 * page.messages
		 *  - update : gets the latest messages
		 *  - render : renders the new messages as html
		 *  - create : calls the new message fly-out
		 *  - other  : fixDateFormat(), formatDate(), formatText()
		 **********************************************************************/
		messages:
		{
			items      : new Array(),
			updateDate : null,
			
			
			//***| triggers get-latest event |**********************************
			
			update: function(refresh)
			{
				// log
				chirpr.logUpdate("main", "page.messages.update", gadget.logging.entryPoint, true, "getting messages" + (refresh ? " w/ refresh" : ""));
				
				// not first update -and- not manual update -and- it's been less than a minute since last update 
				var limiter = false;
				var timeSinceLastUpdate = (page.messages.updateDate == null) ? chirpr.refreshInterval+1 : (new Date().getTime() - page.messages.updateDate.getTime());
				if (page.messages.updateDate != null && refresh && timeSinceLastUpdate < chirpr.refreshInterval)
				{
					limiter = true;
					chirpr.logUpdate("main", "page.messages.update", gadget.logging.exitPoint, true, "update skipped");
				}
				
				// force refresh, if it's been more than refresh interval
				if (!refresh && timeSinceLastUpdate > chirpr.refreshInterval) { refresh = true; chirpr.logUpdate("main", "page.messages.update", "refresh interval passed; restarting refresh loop"); }
				
				// only show "connecting" alert if this is the first update
				if (page.messages.updateDate == null) { page.alerts.show(page.alerts.connecting); }
				
				// if limiter not enabled, get/show messages
				if (!limiter)
				{
					if (refresh) { page.messages.updateDate = new Date(); }
					try
					{
						var success = function(items)
						{
							// log
							chirpr.logUpdate("main", "page.messages.update[success]", gadget.logging.entryPoint);
							
							// show/hide new post link
							page.post.style.display = (items!=null ? 'inline' : 'none');

							// if no items, show "cannot connect" alert; otherwise, show posts
							if (items == null)
							{
								chirpr.logUpdate("main", "page.messages.update[success]", "items array is null", false);
								page.alerts.show(page.alerts.cannotConnect);
								chirpr.logUpdate("main", "page.messages.update[success]", gadget.logging.exitPoint, true, "no messages");
								chirpr.logUpdate("main", "page.messages.update", gadget.logging.exitPoint, true, "no messages");
							}
							else
							{
								chirpr.logUpdate("main", "page.messages.update[success]", items.length + " item(s) returned", true, "rendering messages as HTML");
								page.alerts.hide();
								page.messages.render(items);
								chirpr.logUpdate("main", "page.messages.update[success]", gadget.logging.exitPoint, true, items.length + " message(s)");
								chirpr.logUpdate("main", "page.messages.update", gadget.logging.exitPoint, true, items.length+" message(s)");
							}
						};
						var failed = function(e)
						{
							chirpr.logUpdate("main", "page.messages.update[failed]", "update failed; " + (util.error.isError(e) ? e.description : e), false, "showing alert");
							page.alerts.show(e);
							chirpr.logUpdate("main", "page.messages.update", gadget.logging.exitPoint, false, "error: " + (util.error.isError(e) ? e.description : e));
						};
						
						// get messages
						chirpr.logUpdate("main", "page.messages.update", "getting latest messages, now...");
						chirpr.twitter.getMessages(success, failed);
					}
					catch (e)
					{
						page.alerts.show(e);
						chirpr.logUpdate("main", "page.messages.update", gadget.logging.exitPoint, false, "error: "+e);
					}
				}
				
				// continue to refresh
				// TODO: Need to limit this; for some reason, it seems like 3-4 refreshing processes will be run at once; we should kill limited refreshes if they have been limited 3+ x's
				if (refresh) { setTimeout("page.messages.update(true);", chirpr.refreshInterval); }
				
				// NOTE: exit point is in success and failed functions as well as catch block
			},
			
			
			//***| renders messages as html |***********************************
			
			render: function(items)
			{
				// TODO: See if gadget API has string builder
				
				// log
				chirpr.logUpdate("main", "page.messages.render", gadget.logging.entryPoint, true, "messages="+items);
				
				// if no new messages, return
				if (items == null || typeof(items.length) == "undefined" || items.length == 0) { chirpr.logUpdate("main", "page.messages.render", gadget.logging.exitPoint, true, "no messages"); return; }
				
				// log
				chirpr.logUpdate("main", "page.messages.render", "rendering " + items.length + " message(s)...");
				
				// keep latest 20 messages
				chirpr.logUpdate("main", "page.messages.render", "merging old and new messages...");
				var oldItemIndex = 0;
				var oldItems = page.messages.items;
				page.messages.items = items;
				for (var i=page.messages.items.length; i<chirpr.messageCount && oldItemIndex<oldItems.length; i++)
				{
					page.messages.items[i] = oldItems[oldItemIndex++];
				}
				
				// generate html for all messages
				chirpr.logUpdate("main", "page.messages.render", "building html...");
				var html = "";
				for (i = 0; i < page.messages.items.length; i++)
				{
					var msg = page.messages.items[i];
					html += "<div class='author'>"
						+ 		"<span class='time'>" + page.messages.formatDate(page.messages.fixDateFormat(msg.created_at)) + "</span>"
						+		"<a class='name' href='javascript:page.messages.create(\""+msg.user.screen_name+"\","+msg.id+");' title='"+msg.user.name+"'>"
						+			msg.user.screen_name + "</a>"
						+	"</div>"
						+	"<div class='entry'>" + page.messages.formatText(msg.text) + "</div>";
				}
				
				// save html, show "connected" icons
				page.content.innerHTML     = html;
				page.refresh.style.display = 'inline';
				page.scroll.style.display  = 'block';
				
				// play sound
				if (gadget.host.sidebar && chirpr.soundEnabled())
				{
					System.Sound.playSound(chirpr.notifySound);
				}
				
				// log
				chirpr.logUpdate("main", "page.messages.render", gadget.logging.exitPoint);
			},

			
			//***| opens new message fly-out |**********************************
			
			create: function(user, id)
			{
				chirpr.properties.setReplyToUser(user==null ? "" : user);
				chirpr.properties.setReplyToMessage(user==null ? "" : id);
				gadget.flyout.show(chirpr.pages.message, (gadget.host.browser && user!=null) ? "toUser="+user+"&toMessage="+id : "");
			},

			
			//***| fixes Twitter date format |**********************************
			
			fixDateFormat: function(date)
			{
				/**
				 *  0   1   2     3      4     5
				 * Tue Nov 04 23:43:55 +0000 2008
				 */
				var blocks = date.split(' ');
				var time = blocks[3];
				blocks[3]=blocks[2];
				blocks[2]=blocks[1];
				blocks[1]=blocks[0];
				blocks[0]=time;
				var timezone = blocks[4];
				blocks[4]=blocks[5];
				blocks[5]=timezone;
				return blocks.join(' ');
			},
			
			
			//***| formats message date for html |******************************
			
			formatDate: function(input)
			{
				var date=new Date();
				date.setTime(Date.parse(input));
				var h=date.getHours();
				var m=date.getMinutes();
				var dayNight='am';
				if(h==0){h=12;}else if(h>12){h-=12;dayNight='pm';}
				if(m<10){m='0'+m.toString();}
				return h+':'+m+' '+dayNight;
			},

			
			//***| formats message body for html |******************************
			
			formatText: function(input)
			{
				var index;
				var formatted = "";
				var words = input.split(' ');
				for (index in words)
				{
					var word = words[index];
					if (word.match("http[s]?:"))
					{
						var start = word.indexOf(word.match("http[s]?:")[0]);
						var extra = word.match("[^a-zA-Z0-9/]*$");
						var link = (extra.length == 0) ? word : word.substring(start, word.length - extra[0].length);
						word = ((start > 0) ? word.substring(0, start) : "")
							+ "<a href='" + link + "' title='" + link + "'>[link] <span class='arrow'>ä</span></a>" 
							+ ((extra.length > 0) ? extra[0] : "");
					}
					if (word.match("^@[A-Za-z0-9_]+[^a-zA-Z0-9_]*$"))
					{
						var extra = word.match("[^a-zA-Z0-9_]*$");
						var user = (extra.length == 0) ? word.substring(1) : word.substring(1, word.length - extra[0].length);
						word = "@<a href='http://twitter.com/" + user + "' title='Twitter / " + user + "'>" + user + " <span class='arrow'>ä</span></a>" + ((extra.length > 0) ? extra[0] : "");
					}
					if (word.match("^#[A-Za-z0-9_\\-]+[^a-zA-Z0-9]*$"))
					{
						var extra = word.match("[^a-zA-Z0-9]*$");
						var tag = (extra.length == 0) ? word.substring(1) : word.substring(1, word.length - extra[0].length);
						word = "#<a href='http://search.twitter.com/search?q=%23" + tag + "' title='Twitter search for #" + tag + "'>" + tag + " <span class='arrow'>ä</span></a>" + ((extra.length > 0) ? extra[0] : "");
					}
					formatted += word + " ";
				}
				return formatted;
			}
		}
	};
	//]]></script>
</head>
<body id="main">
		
	<!-- debug -->
	<fieldset id="debug:settings" class="debug" style="float:right; margin:2em;">
		<button onclick="gadget.flyout.show(chirpr.pages.settings);">Settings</button>
		<button onclick="$('#debug:log').html(''); for (var i=0; i<gadget.logging.history.length; i++) { try { page.log(gadget.logging.formatEntry(gadget.logging.history[i])); }catch(e){} }">Refresh Log</button>
		<div id="debug:log" style="background-color:#fff; border:solid 1px #666; font-size:1.1em; height:25em; overflow:scroll; padding:.25em; width:50em;"></div>
	</fieldset>

	<div class="controlPanel">
		<div id="header">
			<a id="post" href="javascript:page.messages.create();" onclick="window.focus();">new chirp <span class="arrow">á</span></a>
			<a id="updateIcon" href="http://chirpr.codeplex.com/Release/ProjectReleases.aspx"><img src="img/update.png" alt="Update available!" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lightenLess(this);"/></a>
			<h1>
				<a id="refresh" href="javascript:page.messages.update();" onclick="window.focus();" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lightenLess(this);"><img src="img/refresh.png" alt="Refresh"/></a>
				<a href="http://twitter.com/chirpr" onclick="window.focus();">chirpr</a>
			</h1>
		</div>
		<div id="resize">
			<a id="size1" href="javascript:page.resize(150);" title="Small"  onclick="window.focus();" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lightenLess(this);">&nbsp;</a>
			<a id="size2" href="javascript:page.resize(225);" title="Medium" onclick="window.focus();" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lightenLess(this);">&nbsp;</a>
			<a id="size3" href="javascript:page.resize(350);" title="Large"  onclick="window.focus();" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lightenLess(this);">&nbsp;</a>
		</div>
	</div>

	<a id="twitterLogo" href="http://twitter.com/home" onclick="window.focus();" onmouseover="util.opacity.darkenMore(this);" onmouseout="util.opacity.lighten(this);"><img src="img/twitter_16x.png" alt="Twitter"/></a>
	<div id="scroll" class="controlPanel">
		<a href="javascript:window.focus();util.scrolling.scroll(page.container.id,-5);"><img src="img/arrowup.png"   alt="Scroll up"   ondblclick="util.scrolling.scroll(page.container.id,-999);" onmouseover="util.opacity.darkenMore(this); util.scrolling.start(page.container.id,-.5);" onmouseout="util.opacity.lighten(this); util.scrolling.stop();"/></a>
		<a href="javascript:window.focus();util.scrolling.scroll(page.container.id,5);" ><img src="img/arrowdown.png" alt="Scroll down" ondblclick="util.scrolling.scroll(page.container.id,999);"  onmouseover="util.opacity.darkenMore(this); util.scrolling.start(page.container.id,.5);"  onmouseout="util.opacity.lighten(this); util.scrolling.stop();"/></a>
	</div>                                                                                                  
                                                                                                                                                                                             
	<div id="container">
		<div id="alert">
			<div id="alertmsg"><b>Error Loading.</b> Remove and re-add from Gadget Gallery.</div>
			<img id="alertimg" src="img/chirpie_left_cob_32x.png" alt="Chirpie"/>
		</div>
		<div id="content"></div>
	</div>
	
	<div>
		<!-- shortcuts kept separate because of focus/click issues -->
		<a accesskey="1" href="javascript:page.fire('size1');"       onfocus="this.click();"></a>
		<a accesskey="2" href="javascript:page.fire('size2');"       onfocus="this.click();"></a>
		<a accesskey="3" href="javascript:page.fire('size3');"       onfocus="this.click();"></a>
		<a accesskey="n" href="javascript:page.fire('post');"        onfocus="this.click();"></a>
		<a accesskey="r" href="javascript:page.fire('refresh');"     onfocus="this.click();"></a>
		<a accesskey="t" href="javascript:page.fire('twitterLogo');" onfocus="this.click();"></a>
	</div>                                                              
	
</body>
</html>
